﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using BluEtch.GotPhone.Helpers;

namespace BluEtch.GotPhone.MVVM
{
    /// <summary>
    /// Clean implementation of an ObservableBase class to be used for any observable data.
    /// </summary>
    public class ObservableBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Implemented for INotifyPropertyChanged.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Stores the actual property values.
        /// </summary>
        private readonly Dictionary<string, object> _propertyValues = new Dictionary<string, object>();

        /// <summary>
        /// Raises the PropertyChanged event given a (string) property name
        /// </summary>
        public void RaisePropertyChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");

            var handler = PropertyChanged;
            if (handler != null)
            {
                Action handle = () => handler(this, new PropertyChangedEventArgs(propertyName));
                Threading.InvokeUI(handle);
            }
        }

        /// <summary>
        /// Raises the PropertyChanged event given an expression of the property
        /// </summary>
        /// <param name="propertyExpression">An expression of the property</param>
        protected virtual void RaisePropertyChanged<TValue>(Expression<Func<TValue>> propertyExpression)
        {
            var property = Internal.GetPropertyInfoFromExpression(propertyExpression);

            var handler = PropertyChanged;
            if (handler != null)
            {
                Action handle = () => handler(this, new PropertyChangedEventArgs(property.Name));
                Threading.InvokeUI(handle);
            }
        }

        /// <summary>
        /// Allows strongly-typed property value retrieval in the form of the following.
        ///     get { return GetValue(() => this.MyProperty); }
        /// </summary>
        protected TValue GetInitializedValue<TValue>(Expression<Func<TValue>> valueSelector)
        {
            var property = Internal.GetPropertyInfoFromExpression(valueSelector);

            object value;
            if (!_propertyValues.TryGetValue(property.Name, out value))
            {
                if (Internal.IsNullable(property.PropertyType) && typeof(TValue) != typeof(String)) //Strings don't have a parameterless constructor
                    _propertyValues.Add(property.Name, Activator.CreateInstance(typeof(TValue)));
                else
                    _propertyValues.Add(property.Name, default(TValue));
            }

            return (TValue)_propertyValues[property.Name];
        }

        /// <summary>
        /// Allows strongly-typed property value retrieval in the form of the following. Allows you to specify a default value.
        ///     get { return GetValue(() => this.MyProperty); }
        /// </summary>
        protected TValue GetValue<TValue>(Expression<Func<TValue>> valueSelector, TValue defaultValue = default(TValue))
        {
            var property = Internal.GetPropertyInfoFromExpression(valueSelector);

            object value;
            if (!_propertyValues.TryGetValue(property.Name, out value))
                _propertyValues.Add(property.Name, defaultValue);

            return (TValue)_propertyValues[property.Name];
        }

        /// <summary>
        /// Allows strongly-typed property assignment in the form of the following.
        ///     set { SetValue(() => this.MyProperty, value); }
        /// </summary>
        protected void SetValue<TValue>(Expression<Func<TValue>> valueSelector, TValue value, Action changeHandler = null)
        {
            var property = Internal.GetPropertyInfoFromExpression(valueSelector);

            TValue oldValue;
            object oldRawValue;
            if (_propertyValues.TryGetValue(property.Name, out oldRawValue))
            {
                oldValue = (TValue)oldRawValue;
            }
            else
            {
                oldValue = default(TValue);
            }

            if (Equals(oldValue, value)) return;

            _propertyValues[property.Name] = value;
            if (changeHandler != null)
            {
                changeHandler();
            }

            RaisePropertyChanged(property.Name);
        }
    }
}
